﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using CommonFramework.Log;
using CommonFramework.Serialize.BytesArray.Private;

namespace CommonFramework.Serialize.BytesArray
{
    public class BytesReader
    {
        private IBytesProvider _bytesProvider;
        public byte[] Bytes() => _bytesProvider.ReadBytesView();
        public int ReadIndex
        {
            get => _bytesProvider.ReadIndex;
            private set => _bytesProvider.ReadIndex = value;
        }

        internal BytesReader(IBytesProvider bytesProvider)
        {
            _bytesProvider = bytesProvider;
        }

        /// <summary>
        /// 剩余的可读字节长度
        /// </summary>
        public int DataLength() => _bytesProvider.DataLength();
        /// <summary>
        /// 每次读出数据后,readIndex都会加上偏移量.如果只是想取出数据且保留数据在字节数组中,可以回退偏移量
        /// </summary>
        public void RevertReadIndex(int revertCount)
        {
            revertCount = Math.Min(ReadIndex, revertCount);
            ReadIndex -= revertCount;
        }

        public void AdvanceReadIndex(int advanceCount)
        {
            ReadIndex = Math.Min(ReadIndex + advanceCount, _bytesProvider.WriteIndex);
        }
        /// <summary>
        /// 已经读过的数据可以被舍弃,把用户数据移动到字节数组头部增加reamin
        /// </summary>
        public void TryMoveDataToOrigin(int threshold = 16)
        {
            if (DataLength() < threshold && ReadIndex >= 3 * threshold)
                _bytesProvider.MoveDataToOrigin();
        }

        /// <summary>
        /// 读出数据到字节数组,返回成功读出的字节数
        /// </summary>
        public int ReadBytes(byte[] target, int offset, int count)
        {
            int readLen = Math.Min(target.Length - offset, count);
            readLen = Math.Min(readLen,DataLength());

            if (readLen > 0)
            {
                Buffer.BlockCopy(Bytes(), ReadIndex, target, offset, readLen);
                ReadIndex += readLen;
            }

            return readLen;
        }
        public int ReadBytes(byte[] target, int offset) => ReadBytes(target, offset, target.Length);
        public int ReadBytes(byte[] target) => ReadBytes(target, 0, target.Length);

        /// <summary>
        /// 读出数据到流中 
        /// </summary>
        public int ReadStream(Stream stream, int count)
        {
            if (!stream.CanWrite)
                return 0;
            int readLen = Math.Min(count, DataLength());
            if (readLen > 0)
                stream.Write(Bytes(), ReadIndex, readLen);
            ReadIndex += readLen;
            return readLen;
        }

        [Conditional("DEBUG")]
        private void CheckDataLenMeet(int atleastLen)
        {
            if(DataLength()<atleastLen)
                throw new OverflowException(
                    $"data length is too short to read,with dataLen={DataLength()},requireLen={atleastLen}");
        }

        #region 读出基础类型
        public byte ReadByte()
        {
            int size = sizeof(byte);
            CheckDataLenMeet(size);
            byte val= Bytes()[ReadIndex];
            ReadIndex += size;
            return val;
        }
        public sbyte ReadSByte()
        {
            int size = sizeof(sbyte);
            CheckDataLenMeet(size);
            sbyte val = (sbyte)Bytes()[ReadIndex];
            ReadIndex += size;
            return val;
        }
        public bool ReadBool()
        {
            int size = sizeof(bool);
            CheckDataLenMeet(size);
            bool val = Bytes()[ReadIndex]>0;
            ReadIndex += size;
            return val;
        }
        public char ReadChar()
        {
            int size = sizeof(char);
            CheckDataLenMeet(size);
            char val = (char)BytesConvertUtil.ToInt16(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public short ReadInt16()
        {
            int size = sizeof(short);
            CheckDataLenMeet(size);
            short val =BytesConvertUtil.ToInt16(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public ushort ReadUInt16()
        {
            int size = sizeof(ushort);
            CheckDataLenMeet(size);
            ushort val = BytesConvertUtil.ToUInt16(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public int ReadInt32()
        {
            int size = sizeof(int);
            CheckDataLenMeet(size);
            int val = BytesConvertUtil.ToInt32(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public uint ReadUInt32()
        {
            int size = sizeof(uint);
            CheckDataLenMeet(size);
            uint val = BytesConvertUtil.ToUInt32(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public long ReadInt64()
        {
            int size = sizeof(long);
            CheckDataLenMeet(size);
            long val = BytesConvertUtil.ToInt64(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public ulong ReadUInt64()
        {
            int size = sizeof(ulong);
            CheckDataLenMeet(size);
            ulong val = BytesConvertUtil.ToUInt64(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public float ReadFloat()
        {
            int size = sizeof(float);
            CheckDataLenMeet(size);
            float val = BytesConvertUtil.ToSingle(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public double ReadDouble()
        {
            int size = sizeof(double);
            CheckDataLenMeet(size);
            double val = BytesConvertUtil.ToDouble(Bytes(), ReadIndex);
            ReadIndex += size;
            return val;
        }
        public string ReadString()
        {
            int size = ReadInt32();
            if (size <= 0)
            {
                return string.Empty;
            }
            CheckDataLenMeet(size);

            string result = Encoding.UTF8.GetString(Bytes(), ReadIndex, size);
            ReadIndex += size;
            return result;
        }
        #endregion

        public T ReadSerializable<T>() where T : IBytesSerializable, new()
        {
            T val = new T();
            val.ReadFromBytes(this);
            return val;
        }

        #region 读元素泛型

        public object ReadPrimitive(Type primitiveType)
        {
            if (primitiveType.IsPrimitive == false)
                return Activator.CreateInstance(primitiveType); //default value

            var typeCode = Type.GetTypeCode(primitiveType);
            switch (typeCode)
            {
                case TypeCode.Boolean:
                    return ReadBool();
                case TypeCode.Char:
                    return ReadChar();
                case TypeCode.SByte:
                    return ReadSByte();
                case TypeCode.Byte:
                    return ReadByte();
                case TypeCode.Int16:
                    return ReadInt16();
                case TypeCode.UInt16:
                    return ReadUInt16();
                case TypeCode.Int32:
                    return ReadInt32();
                case TypeCode.UInt32:
                    return ReadUInt32();
                case TypeCode.Int64:
                    return ReadInt64();
                case TypeCode.UInt64:
                    return ReadUInt64();
                case TypeCode.Single:
                    return ReadFloat();
                case TypeCode.Double:
                    return ReadDouble();
                case TypeCode.String:
                    return ReadString();
                default:
                    LogUtil.LogWarning("unsupport type for BytesReader,with type="+ primitiveType);
                    break;
            }

            return Activator.CreateInstance(primitiveType); //default value
        }
        public object ReadPrimitive<T>() => ReadPrimitive(typeof(T));
        public object Read(Type type)
        {
            //null flag
            bool isNUll = ReadBool();
            if(isNUll)
                return null;

            if (type.IsPrimitive)
            {
                return ReadPrimitive(type);
            }
            else if (typeof(IBytesSerializable).IsAssignableFrom(type))
            {
                var serializeObj = Activator.CreateInstance(type) as IBytesSerializable;
                serializeObj?.ReadFromBytes(this);
                return serializeObj;
            }
            else if (type == typeof(string))
            {
                return ReadString();
            }
            else if(type.IsArray)
            {
                return ReadArray(type.GetElementType());
            }
            else if (type.IsGenericType)
            {
                Type genericType= type.GetGenericTypeDefinition();
                if (genericType == typeof(List<>)) //List容器类型
                {
                    return ReadList(type.GetGenericArguments().First());
                }
                else if (genericType == typeof(Dictionary<,>)) //映射类型
                {
                    var genericArgs= type.GetGenericArguments();
                    return ReadDictionary(genericArgs[0], genericArgs[1]);
                }
                else
                {
                    LogUtil.LogNotReachHereError("unsupport generic type");
                }
            }
            return Activator.CreateInstance(type);
        }
        #endregion

        #region 读容器泛型

        public object[] ReadArray(Type type)
        {
            short arraySize = ReadInt16();
            object[] arr = new object[arraySize];
            for (int i = 0; i < arraySize; ++i)
            {
                arr[i] = Read(type);
            }
            return arr;
        }
        public T[] ReadArray<T>()
        {
            Type type= typeof(T);
            short arraySize = ReadInt16();
            T[] arr = new T[arraySize];
            for (int i = 0; i < arraySize; ++i)
            {
                arr[i] =(T)Read(type);
            }
            return arr;
        }
        public List<object> ReadList(Type type)
        {
            short size = ReadInt16();
            List<object> list = new List<object>(size);
            for (int i = 0; i < size; ++i)
            {
                list.Add(Read(type));
            }
            return list;
        }
        public List<T> ReadList<T>()
        {
            Type type = typeof(T);
            short size = ReadInt16();
            List<T> list = new List<T>(size);
            for (int i = 0; i < size; ++i)
            {
                list.Add((T)Read(type));
            }
            return list;
        }
        public Dictionary<object, object> ReadDictionary(Type keyType, Type valueType)
        {
            Dictionary<object, object> dic = new Dictionary<object, object>();
            short size = ReadInt16();
            for (int i = 0; i < size; ++i)
            {
                object key = Read(keyType);
                object value = Read(valueType);
                dic.Add(key,value);
            }
            return dic;
        }
        public Dictionary<Key_T, Val_T> ReadDictionary<Key_T, Val_T>()
        {
            Dictionary<object, object> dic = ReadDictionary(typeof(Key_T), typeof(Val_T));
            
            Dictionary<Key_T, Val_T> val = new Dictionary<Key_T, Val_T>();
            foreach (var kvp in dic)
            {
                val.Add((Key_T)kvp.Key,(Val_T)kvp.Value);    
            }
            return val;
        }

        #endregion
    }
}